package com.iagent.request;

import com.iagent.bean.IagentBeanWrapper;
import com.iagent.bean.IagentFile;
import com.iagent.bean.IagentParamBean;
import com.iagent.bean.RequestParameter;
import com.iagent.exception.NotFoundResultResolverException;
import com.iagent.factory.IagentConfiguration;
import com.iagent.logging.LogFactory;
import com.iagent.logging.Logger;
import com.iagent.resovler.parameter.ParameterResolver;
import com.iagent.resovler.result.ResultResolver;
import com.iagent.util.ClassUtils;
import com.iagent.util.CollectionUtils;
import com.iagent.util.ReflectUtils;
import com.iagent.util.StringUtils;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * <b> the Abstract Common Http Request Executor</b>
 */
public abstract class AbstractHttpExecutor implements HttpExecutor {

    private static final Logger logger = LogFactory.getLogger(AbstractHttpExecutor.class);

    protected IagentConfiguration configuration;

    // 参数解析器
    private List<ParameterResolver> parameterResolvers = new ArrayList<>();

    // 结果参数解析器
    private List<ResultResolver> resultResolvers = new ArrayList<>();

    // 初始化状态
    private AtomicBoolean state = new AtomicBoolean(false);

    /**
     * 设置配置对象
     * @param configuration
     */
    protected void setConfiguration(IagentConfiguration configuration) {
        this.configuration = configuration;
    }

    private void init() {
        if (!state.get()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Initialize all parameter resolver");
            }
            // 初始化参数解析器
            List<Class<ParameterResolver>> parameterClassListByClass = this.configuration.getAliasRegister().getAliasClassListByClass(ParameterResolver.class);
            CollectionUtils.sortByOrder(parameterClassListByClass);
            for (Class<ParameterResolver> parameterResolverClass : parameterClassListByClass) {
                ParameterResolver parameterResolver = ClassUtils.newInstance(parameterResolverClass);
                ReflectUtils.setFieldValue(parameterResolver, "configuration", this.configuration);
                if (logger.isDebugEnabled()) {
                    logger.debug("Load parameter resolver is " + parameterResolver);
                }
                this.parameterResolvers.add(parameterResolver);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Initialize all result resolver");
            }
            // 初始化结果解析器
            List<Class<ResultResolver>> resultClassListByClass = this.configuration.getAliasRegister().getAliasClassListByClass(ResultResolver.class);
            CollectionUtils.sortByOrder(resultClassListByClass);
            for (Class<ResultResolver> resultResolverClass : resultClassListByClass) {
                ResultResolver resultResolver = ClassUtils.newInstance(resultResolverClass);
                ReflectUtils.setFieldValue(resultResolver, "configuration", this.configuration);
                if (logger.isDebugEnabled()) {
                    logger.debug("Load result resolver is " + resultResolver);
                }
                this.resultResolvers.add(resultResolver);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("The size of parameter resolver is " + this.parameterResolvers.size());
                logger.debug("The size of result resolver is " + this.resultResolvers.size());
            }
            state.compareAndSet(false, true);
        }
    }

    /**
     * 1. Execute the parameter parser
     * 2. Encapsulate parameters to execute subclass methods
     * 3. Obtain the execution result of the actuator
     * 4. Return the execution result through the result parser
     */
    @Override
    public Object sendHttp(IagentBeanWrapper bean, Object[] args) throws Throwable {
        init();
        // 使用参数解析器处理接口请求参数
        runParameterResolver(args);
        // 解析path key
        String url = parsePathVariable(bean, args);
        // 获取到RequestParameter
        RequestParameter requestParameter = getRequestParameter(bean.getParamBean(), args);
        if (logger.isDebugEnabled()) {
            logger.debug("[" + ClassUtils.getMethodName(bean.getBean().getMethod()) + "] The request parameter is " + requestParameter);
        }
        // 执行具体的子类方法
        byte[] responseBody = this.execute(url, bean.getBean().getRequestConfig(), requestParameter);
        if (logger.isDebugEnabled()) {
            logger.debug("[" + ClassUtils.getMethodName(bean.getBean().getMethod()) + "] The size of the execution result of the executor is " + responseBody.length);
        }
        // 结果解析器处理器
        return runResultResolver(bean, responseBody);
    }

    /**
     * Execute the parameter parser
     */
    private void runParameterResolver(Object[] args) throws Throwable {
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                if (logger.isDebugEnabled()) {
                    logger.debug("Resolver parameter is " + arg);
                }
                for (ParameterResolver parameterResolver : this.parameterResolvers) {
                    arg = parameterResolver.resolver(arg);
                }
                args[i] = arg;
            }
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("The args parameter is null");
            }
        }
    }

    /**
     * Parse path parameters
     * @return return parse url
     */
    private String parsePathVariable(IagentBeanWrapper wrapper, Object[] args) {
        Map<String, Integer> pathMap = wrapper.getParamBean().getPathIndex();
        String url = wrapper.getBean().getUrl();
        if (logger.isDebugEnabled()) {
            logger.debug("[" + ClassUtils.getMethodName(wrapper.getBean().getMethod()) +"] Parse path parameter, the url is " + url);
        }
        if (pathMap != null && !pathMap.isEmpty()) {
            for (Map.Entry<String, Integer> entry : pathMap.entrySet()) {
                String pathKey = entry.getKey();
                if (logger.isDebugEnabled()) {
                    logger.debug("[" + ClassUtils.getMethodName(wrapper.getBean().getMethod()) +"] Parse path variable key is " + pathKey);
                }
                url = url.replaceAll("\\{" + pathKey + "\\}", String.valueOf(args[entry.getValue().intValue()]));
            }
            if (url.contains("{") && url.contains("}")) {
                // 如果还存在参数未解析到，则提醒
                logger.warn("[" + ClassUtils.getMethodName(wrapper.getBean().getMethod()) +"] The url has not resolver parameter [" + StringUtils.getStringByTags(url, "{", "}") + "]");
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("[" + ClassUtils.getMethodName(wrapper.getBean().getMethod()) +"] After parsing url is " + url);
        }
        return url;
    }

    /**
     * Execute the result parser
     * @param data
     * @return Returns the parser result
     */
    private Object runResultResolver(IagentBeanWrapper wrapper, byte[] data) throws Throwable {
        if (logger.isDebugEnabled()) {
            logger.debug("[" + ClassUtils.getMethodName(wrapper.getBean().getMethod()) +"] The wrapper return class type is " + wrapper.getBean().getReturnClass());
        }
        for (ResultResolver resultResolver : this.resultResolvers) {
            if (resultResolver.isResolver(wrapper.getBean().getReturnClass())) {
                if (logger.isDebugEnabled()) {
                    logger.debug("[" + ClassUtils.getMethodName(wrapper.getBean().getMethod()) +"] The current parser that satisfies the result is " + resultResolver);
                }
                return resultResolver.resolver(data, wrapper.getBean().getReturnClass());
            }
        }
        throw new NotFoundResultResolverException("[" + ClassUtils.getMethodName(wrapper.getBean().getMethod()) +"] Not found result resolver");
    }

    /**
     * get request parameter object from wrapper
     *
     * @return RequestParameter
     * @see RequestParameter
     */
    private RequestParameter getRequestParameter(IagentParamBean paramBean, Object[] args) {
        if (logger.isDebugEnabled()) {
            logger.debug("Get request parameter object");
        }
        RequestParameter parameter = RequestParameter.createBuilder()
                .headers(paramBean.getHeaderIndex(), args)
                .form(paramBean.getParamIndex(), args)
                .body(paramBean.getBodyIndex(), args)
                .build();
        return parameter;
    }

    /**
     * exists file type or the array of byte, if exists than true else false
     *
     * @param parameter
     * @return
     */
    protected boolean existsFileType(RequestParameter parameter) {
        Map<String, Object> form = parameter.getForm();
        if (form != null && !form.isEmpty()) {
            Collection<Object> values = form.values();
            for (Object value : values) {
                if (value instanceof File || value instanceof byte[] || value instanceof InputStream || value instanceof IagentFile) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * specific http request
     * Update parameter of method from 2.2.0 version
     *
     * @param url Real request url, PathKey has already been processed
     * @param config default request config
     * @param data request parameter
     * @return byte[] return byte array
     * @since 2.2.0
     */
    public abstract byte[] execute(String url, RequestConfig config, RequestParameter data) throws Throwable;

}
